﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace CRMBiz.CalculateInterest
{
    public static class SACC
    {
        public static double MonthlyFee
        {
            get;
            set;
        }
        public static double EstablishmentFee
        {
            get;
            set;
        }
        public static double TotalMonthlyFee
        {
            get;
            set;
        }
        public static DateTime LastPaymentDate
        {
            get;
            set;
        }

        public static DataTable SACCCalculateByTerm(double loanAmount, int term, int repaymentOpt, double establishFee, double totMonthlyFee, DateTime settlementDate, DateTime firstPaymentDate)
        {
            DataTable dtSchedule = new DataTable();
            double firstPaymentMonthlyFee = GetFirstPaymentMonthlyFee(settlementDate, firstPaymentDate);
            double firstCharge = loanAmount+establishFee + firstPaymentMonthlyFee;
            decimal totLoanAmount = Convert.ToDecimal(loanAmount + establishFee + SACC.TotalMonthlyFee);
            DateTime lastPaymentDate = new DateTime();
            int paymentTerm = GetPaymentTerm(settlementDate, firstPaymentDate, term, repaymentOpt,out lastPaymentDate);
            decimal instalmentAmt = Convert.ToDecimal(Math.Round(totLoanAmount / paymentTerm, 2));
          
            dtSchedule.Columns.Add("DueDate", typeof(DateTime));
            dtSchedule.Columns.Add("Repayment", typeof(decimal));
            dtSchedule.Columns.Add("Feecharge", typeof(decimal));
            dtSchedule.Columns.Add("Balance", typeof(decimal));

            if (totLoanAmount - instalmentAmt * paymentTerm > 0)
            {
                instalmentAmt =instalmentAmt+ 0.01m;
            }

            decimal balanceAmt = Convert.ToDecimal(firstCharge);
            int skipMonth = Convert.ToInt16(firstPaymentMonthlyFee / SACC.MonthlyFee);
            DateTime nextMonthlyFeeChargeDate = GetNextMonthlyFeeChargeDate(settlementDate, skipMonth);
            
            double feeCharge = 0;

            for (int count = 0; count < paymentTerm; count++)
            {
                feeCharge = 0;
                DataRow dr = dtSchedule.NewRow();
                DateTime nextPaymentDate = DateTime.Now;

                if (repaymentOpt == 30)
                {
                    nextPaymentDate = firstPaymentDate.AddMonths(count);
                }
                else
                {
                    nextPaymentDate = firstPaymentDate.AddDays(count * repaymentOpt);
                }

                if (nextPaymentDate >= nextMonthlyFeeChargeDate)
                {
                    skipMonth += 1;
                    balanceAmt += Convert.ToDecimal(SACC.MonthlyFee);
                    feeCharge = SACC.MonthlyFee;
                    DataRow monthlyFeeRow = dtSchedule.NewRow();
                    monthlyFeeRow["DueDate"] = nextMonthlyFeeChargeDate;
                    monthlyFeeRow["Feecharge"] = String.Format("{0:0.00}", feeCharge);
                    monthlyFeeRow["Balance"] = String.Format("{0:0.00}", balanceAmt);
                    nextMonthlyFeeChargeDate = GetNextMonthlyFeeChargeDate(settlementDate, skipMonth);

                    dtSchedule.Rows.Add(monthlyFeeRow);
                }

                if (balanceAmt - instalmentAmt < 0)
                {
                    instalmentAmt = balanceAmt;
                    balanceAmt = 0;
                }
                else
                {
                    balanceAmt = balanceAmt - instalmentAmt;
                }

                dr["DueDate"] = nextPaymentDate;
                dr["Repayment"] =String.Format("{0:0.00}", instalmentAmt);
                dr["Balance"] = String.Format("{0:0.00}", balanceAmt);  

                dtSchedule.Rows.Add(dr);
            }

            return dtSchedule;
        }
        /// <summary>
        /// Get next monthly fee charge date.
        /// </summary>
        /// <param name="currentChargeDate"></param>
        /// <param name="skipMonth"></param>
        /// <returns></returns>
        private static DateTime GetNextMonthlyFeeChargeDate(DateTime currentChargeDate, int skipMonth)
        {
            DateTime nextMonthChargeDate = currentChargeDate.AddMonths(skipMonth);
            int lastDayOfNextMonth = DateTime.DaysInMonth(nextMonthChargeDate.Year, nextMonthChargeDate.Month);

            if (currentChargeDate.Day > lastDayOfNextMonth)
            {
                DateTime monthAfter = nextMonthChargeDate.AddMonths(1);
                nextMonthChargeDate = new DateTime(monthAfter.Year, monthAfter.Month, 1, 0, 0, 0, monthAfter.Kind);
            }

            return nextMonthChargeDate;
        }
        /// <summary>
        /// Get monthly fee for first payment. Sometime could be more than 1.
        /// </summary>
        /// <param name="settlementDate"></param>
        /// <param name="firstPaymentDate"></param>
        /// <returns></returns>
        private static double GetFirstPaymentMonthlyFee(DateTime settlementDate, DateTime firstPaymentDate)
        {
            int monthDifference = (firstPaymentDate.Month - settlementDate.Month) + 12 * (firstPaymentDate.Year - settlementDate.Year);

            if (monthDifference == 0)
            {
                return SACC.MonthlyFee;
            }
            else
            {
                if (firstPaymentDate.Day >= settlementDate.Day)
                {
                   monthDifference+=1;
                }
            }

            return SACC.MonthlyFee * monthDifference;
        }
        /// <summary>
        /// Get establishment fee
        /// </summary>
        /// <param name="loanAmount"></param>
        /// <param name="establishFeePC"></param>
        /// <returns></returns>
        public static double GetTotalEstablishmentFee(double loanAmount, double establishFeePC)
        {
            SACC.EstablishmentFee = Math.Round(loanAmount * (establishFeePC / 100), 2);
            return SACC.EstablishmentFee;
        }
        /// <summary>
        /// Calculate how many monthes between first payment and last payment
        /// </summary>
        /// <param name="term"></param>
        /// <param name="repaymentOpt"></param>
        /// <param name="settlementDate"></param>
        /// <param name="firstPaymentDate"></param>
        /// <returns></returns>
        public static int GetPaymentTermsByMonth(int term, int repaymentOpt, DateTime settlementDate, DateTime firstPaymentDate)
        {
            try
            {
                DateTime lastPaymentDate = GetLastPaymentDate(settlementDate, firstPaymentDate, repaymentOpt, term);
                DateTime temp = settlementDate;
                int paymentTermsByMonth = 0;

                while (temp <= lastPaymentDate)
                {
                    paymentTermsByMonth += 1;
                    temp = temp.AddMonths(1);
                }

                return paymentTermsByMonth;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// Get last payment date
        /// </summary>
        /// <param name="firstPaymentDate"></param>
        /// <param name="paymentTerm"></param>
        /// <param name="repaymentOpt"></param>
        /// <returns></returns>
        public static DateTime GetLastPaymentDate(DateTime settlementDate, DateTime firstPaymentDate, int repaymentOpt,int term)
        {
            try
            {
                DateTime lastPaymentDate = new DateTime();
                int paymentTerm = GetPaymentTerm(settlementDate, firstPaymentDate, term, repaymentOpt, out lastPaymentDate);
                return lastPaymentDate;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Calculate total monthly fee.
        /// </summary>
        /// <param name="loanAmount"></param>
        /// <param name="term"></param>
        /// <param name="repaymentOpt"></param>
        /// <param name="monthlyFeePC"></param>
        /// <param name="settlementDate"></param>
        /// <param name="firstPaymentDate"></param>
        /// <returns></returns>
        public static double GetTotalMonthlyFee(double loanAmount, int term, int repaymentOpt, double monthlyFeePC, DateTime settlementDate, DateTime firstPaymentDate)
        {
            SACC.MonthlyFee = Math.Round((loanAmount * (monthlyFeePC / 100)), 2);
            int paymentTermsByMonth = GetPaymentTermsByMonth(term, repaymentOpt, settlementDate, firstPaymentDate);
            SACC.TotalMonthlyFee = SACC.MonthlyFee * paymentTermsByMonth;
            return SACC.TotalMonthlyFee;
        }
        /// <summary>
        /// Calculate how many payments in contract
        /// </summary>
        /// <param name="settlementDate"></param>
        /// <param name="firstPaymentDate"></param>
        /// <param name="term"></param>
        /// <param name="repaymentOpt"></param>
        /// <returns></returns>
        private static int GetPaymentTerm(DateTime settlementDate,DateTime firstPaymentDate, int term, int repaymentOpt, out DateTime lastPaymentDate)
        {
            DateTime contractDueDate = GetContractDueDate(settlementDate, term);
            DateTime paymentDate = firstPaymentDate;
            int paymentTerm = 1;

            while (paymentDate < contractDueDate)
            {
                if ((contractDueDate.Date - paymentDate.Date).TotalDays < repaymentOpt)
                {
                    break;
                }
                else
                {
                    if (repaymentOpt == 30)
                    {
                        paymentDate = paymentDate.AddMonths(1);
                    }
                    else
                    {
                        paymentDate = paymentDate.AddDays(repaymentOpt);
                    }
                    paymentTerm = paymentTerm + 1;
                }
            }

            lastPaymentDate = paymentDate;

            if (paymentTerm > term)
            {
                paymentTerm = term;
            }
            return paymentTerm;
        }
      
        /// <summary>
        /// Get due date of contract.
        /// </summary>
        /// <param name="settlementDate"></param>
        /// <param name="term"></param>
        /// <param name="repaymentOpt"></param>
        /// <returns></returns>
        private static DateTime GetContractDueDate(DateTime settlementDate, int term)
        {
            return settlementDate.AddDays(term * 7); ;
        }
    }
}
